﻿using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using Sasa.QualityTools.CodeAnalysis.Core.Models;
using Sasa.QualityTools.CodeAnalysis.Core.Tests.Unit.Instruments;

namespace Sasa.QualityTools.CodeAnalysis.Core.Tests.Unit.Models
{
    [TestFixture]
    public class FxCopReportTest
    {
        private FxCopReport testee;


        [SetUp]
        public void SetUp()
        {
            testee = new FxCopReport();
        }

        [Test]
        public void BuildupProblemTemplateShouldReturnCorrectTypeProblem()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "BuildupProblemTemplate_TypeProblem.xml");
            var document = new XmlDocument();
            document.Load(path);

            var problem = testee.BuildupProblemTemplate(document.SelectSingleNode("//Message"));

            Assert.AreEqual("CA0001", problem.CheckId);
            Assert.AreEqual("category001", problem.Category);
            Assert.AreEqual("YYY", problem.Id);
            Assert.AreEqual(CodeKind.Class, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
        }

        [Test]
        public void BuildupProblemTemplateShouldReturnCorrectMemberProblem()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "BuildupProblemTemplate_MemberProblem.xml");
            var document = new XmlDocument();
            document.Load(path);

            var problem = testee.BuildupProblemTemplate(document.SelectSingleNode("//Message"));

            Assert.AreEqual("CA0002", problem.CheckId);
            Assert.AreEqual("category002", problem.Category);
            Assert.AreEqual("ZZZ", problem.Id);
            Assert.AreEqual(CodeKind.Method, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
        }

        [Test]
        public void LoadProblemsShouldLoadAllIssueNodes()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "LoadProblems.xml");
            var document = new XmlDocument();
            document.Load(path);

            testee.LoadProblems(document.DocumentElement);

            Assert.AreEqual(7, testee.Problems.Count);

            var problem = testee.Problems.ElementAt(0);
            Assert.AreEqual("CA0001", problem.CheckId);
            Assert.AreEqual("category001", problem.Category);
            Assert.AreEqual("YYY", problem.Id);
            Assert.AreEqual(CodeKind.Class, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
            Assert.AreEqual("issue001-1", problem.Message);
            Assert.AreEqual(null, problem.Path);
            Assert.AreEqual(null, problem.Line);

            problem = testee.Problems.ElementAt(1);
            Assert.AreEqual("CA0001", problem.CheckId);
            Assert.AreEqual("category001", problem.Category);
            Assert.AreEqual("YYY", problem.Id);
            Assert.AreEqual(CodeKind.Class, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
            Assert.AreEqual("issue001-2", problem.Message);
            Assert.AreEqual(null, problem.Path);
            Assert.AreEqual(256, problem.Line);

            problem = testee.Problems.ElementAt(2);
            Assert.AreEqual("CA0002", problem.CheckId);
            Assert.AreEqual("category002", problem.Category);
            Assert.AreEqual("YYY", problem.Id);
            Assert.AreEqual(CodeKind.Class, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
            Assert.AreEqual("issue002-1", problem.Message);
            Assert.AreEqual(null, problem.Path);
            Assert.AreEqual(null, problem.Line);

            problem = testee.Problems.ElementAt(3);
            Assert.AreEqual("CA0003", problem.CheckId);
            Assert.AreEqual("category003", problem.Category);
            Assert.AreEqual("ZZZ", problem.Id);
            Assert.AreEqual(CodeKind.Method, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
            Assert.AreEqual("issue003-1", problem.Message);
            Assert.AreEqual("C:\\foo\\bar.txt", problem.Path);
            Assert.AreEqual(null, problem.Line);

            problem = testee.Problems.ElementAt(4);
            Assert.AreEqual("CA0003", problem.CheckId);
            Assert.AreEqual("category003", problem.Category);
            Assert.AreEqual("ZZZ", problem.Id);
            Assert.AreEqual(CodeKind.Method, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
            Assert.AreEqual("issue003-2", problem.Message);
            Assert.AreEqual(null, problem.Path);
            Assert.AreEqual(null, problem.Line);

            problem = testee.Problems.ElementAt(5);
            Assert.AreEqual("CA0003", problem.CheckId);
            Assert.AreEqual("category003", problem.Category);
            Assert.AreEqual("ZZZ", problem.Id);
            Assert.AreEqual(CodeKind.Method, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
            Assert.AreEqual("issue003-3", problem.Message);
            Assert.AreEqual(null, problem.Path);
            Assert.AreEqual(null, problem.Line);

            problem = testee.Problems.ElementAt(6);
            Assert.AreEqual("CA0004", problem.CheckId);
            Assert.AreEqual("category004", problem.Category);
            Assert.AreEqual("ZZZ", problem.Id);
            Assert.AreEqual(CodeKind.Method, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
            Assert.AreEqual("issue004-1", problem.Message);
            Assert.AreEqual(null, problem.Path);
            Assert.AreEqual(null, problem.Line);
        }

        [Test]
        public void LoadProblemsShouldFilterOutProblemsThatAreMatchedPatternWhenFilterIsSpecified()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "LoadProblemsWithFilter.xml");
            var document = new XmlDocument();
            document.Load(path);

            testee.Filter = new CodeAnalysisResultValueFileNameFilter(new string[] { "^bar[1-3]\\.txt$" });
            testee.LoadProblems(document.DocumentElement);

            Assert.AreEqual(2, testee.Problems.Count);

            var problem = testee.Problems.ElementAt(0);
            Assert.AreEqual("CA0002", problem.CheckId);
            Assert.AreEqual("category002", problem.Category);
            Assert.AreEqual("YYY", problem.Id);
            Assert.AreEqual(CodeKind.Class, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
            Assert.AreEqual("issue002-1", problem.Message);
            Assert.AreEqual("C:\\tmp\\bar4.txt", problem.Path);
            Assert.AreEqual(null, problem.Line);

            problem = testee.Problems.ElementAt(1);
            Assert.AreEqual("CA0004", problem.CheckId);
            Assert.AreEqual("category004", problem.Category);
            Assert.AreEqual("ZZZ", problem.Id);
            Assert.AreEqual(CodeKind.Method, problem.Kind);
            Assert.AreEqual("XXX.YYY", problem.TypeName);
            Assert.AreEqual("target.dll", problem.AssemblyPath);
            Assert.AreEqual("issue004-1", problem.Message);
            Assert.AreEqual(null, problem.Path);
            Assert.AreEqual(null, problem.Line);
        }
        [Test]
        public void BuildupRuleShouldReadAllAttributesAndChildElementsOfRuleElement()
        {
            var document = new XmlDocument();
            document.LoadXml(
                "<Rule CheckId='CA0001' Category='category001' TypeName='type'>" +
                "    <Name>XXX</Name>" +
                "    <Description>YYY</Description>" +
                "    <Url>ZZZ</Url>" +
                "</Rule>");

            var rule = testee.BuildupRule(document.DocumentElement);

            Assert.AreEqual("CA0001", rule.CheckId);
            Assert.AreEqual("category001", rule.Category);
            Assert.AreEqual("type", rule.TypeName);
            Assert.AreEqual("XXX", rule.Name);
            Assert.AreEqual("YYY", rule.Description);
            Assert.AreEqual("ZZZ", rule.Url);
        }

        [Test]
        public void BuildupRuleShouldUseEmptyStringsAsAlternateValuesWhenAttributesAndChildElementsOfRuleElementAreNotSet()
        {
            var document = new XmlDocument();
            document.LoadXml("<Rule></Rule>");

            var rule = testee.BuildupRule(document.DocumentElement);

            Assert.AreEqual("", rule.CheckId);
            Assert.AreEqual("", rule.Category);
            Assert.AreEqual("", rule.TypeName);
            Assert.AreEqual("", rule.Name);
            Assert.AreEqual("", rule.Description);
            Assert.AreEqual("", rule.Url);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void BuildupRuleShouldThrowExceptionlWhenPassedRuleNodeIsNull()
        {
           testee.BuildupRule(null);
        }

        [Test]
        public void LoadRulesShouldLoadAllRuleNodes()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "LoadRules.xml");
            var document = new XmlDocument();
            document.Load(path);

            testee.LoadRules(document.DocumentElement);

            Assert.AreEqual(3, testee.Rules.Count);

            var rule = testee.Rules.ElementAt(0);
            Assert.AreEqual("CA0001", rule.CheckId);
            Assert.AreEqual("category001", rule.Category);
            Assert.AreEqual("type001", rule.TypeName);
            Assert.AreEqual("name001", rule.Name);
            Assert.AreEqual("description001", rule.Description);
            Assert.AreEqual("url001", rule.Url);

            rule = testee.Rules.ElementAt(1);
            Assert.AreEqual("CA0002", rule.CheckId);
            Assert.AreEqual("category002", rule.Category);
            Assert.AreEqual("type002", rule.TypeName);
            Assert.AreEqual("name002", rule.Name);
            Assert.AreEqual("description002", rule.Description);
            Assert.AreEqual("url002", rule.Url);

            rule = testee.Rules.ElementAt(2);
            Assert.AreEqual("CA0003", rule.CheckId);
            Assert.AreEqual("category003", rule.Category);
            Assert.AreEqual("type003", rule.TypeName);
            Assert.AreEqual("name003", rule.Name);
            Assert.AreEqual("description003", rule.Description);
            Assert.AreEqual("url003", rule.Url);
        }

        [Test]
        public void BuildupAnalysisErrorShouldReadAllElementsAndChildNodesOfExceptionNode()
        {
            var document = new XmlDocument();
            document.LoadXml(
                "<Exception Keyword='CA0001'>" +
                "    <ExceptionMessage>message</ExceptionMessage>" +
                "</Exception>");

            var error = testee.BuildupAnalysisError(document.DocumentElement);

            Assert.AreEqual("CA0001", error.Id);
            Assert.AreEqual("message", error.Message);
        }

        [Test]
        public void BuildupAnalysisErrorShouldUseEmptyStringsAsAlternateValuesWhenAttributesAndChildElementsOfExceptionElementAreNotSet()
        {
            var document = new XmlDocument();
            document.LoadXml("<Exception></Exception>");

            var error = testee.BuildupAnalysisError(document.DocumentElement);

            Assert.AreEqual("", error.Id);
            Assert.AreEqual("", error.Message);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void BuildupAnalysisErrorShouldThrowExceptionWhenPassedNodeIsNull()
        {
            testee.BuildupAnalysisError(null);
        }

        [Test]
        public void LoadAnalysisErrrosShouldLoadAllExceptionNodes()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "LoadAnalysisErrors.xml");
            var document = new XmlDocument();
            document.Load(path);

            testee.LoadAnalysisErrors(document.DocumentElement);

            Assert.AreEqual(3, testee.Errors.Count);

            var error = testee.Errors.ElementAt(0);
            Assert.AreEqual("CA0001", error.Id);
            Assert.AreEqual("message001", error.Message);

            error = testee.Errors.ElementAt(1);
            Assert.AreEqual("CA0002", error.Id);
            Assert.AreEqual("message002", error.Message);

            error = testee.Errors.ElementAt(2);
            Assert.AreEqual("CA0003", error.Id);
            Assert.AreEqual("message003", error.Message);
        }

        [TestCase(null)]
        [TestCase("")]
        [TestCase(".\\notexist.xml")]
        [ExpectedException(typeof(InvalidOperationException))]
        public void LoadShouldThrowExceptionWhenPathIsInvalid(string path)
        {
            testee.Load(path);
        }

        [Test]
        [ExpectedException(typeof(CodeAnalysisException))]
        public void LoadShouldThrowExceptionWhenPassedPathOfBrokenXml()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "BrokenXml.xml");
            testee.Load(path);
        }

        [Test]
        public void LoadShouldLoadElementsCorrectly()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "Load.xml");
            testee.Load(path);

            Assert.AreEqual(7, testee.Problems.Count);
            Assert.AreEqual(3, testee.Rules.Count);
            Assert.AreEqual(3, testee.Errors.Count);
        }

        [Test]
        public void LoadShouldCallCoordinateOfPassedCoordinators()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "Load.xml");
            var coordinator1 = new DummyCoordinator1();
            var coordinator2 = new DummyCoordinator2();
            testee.Coordinators.Add(coordinator1);
            testee.Coordinators.Add(coordinator2);

            testee.Load(path);

            Assert.IsTrue(coordinator1.IsUsed);
            Assert.IsTrue(coordinator2.IsUsed);
        }

        [Test]
        public void LoadShouldPassAllProblemsToPassedCoordinator()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "Resources", "Models", "FxCopReport", "Load.xml");
            var problems = new List<Problem>();
            var coordinator = new DummyCoordinator3();
            coordinator.CoordinateRequired += (sender, args) => problems.Add(args.Problem);
            testee.Coordinators.Add(coordinator);

            testee.Load(path);

            CollectionAssert.AreEquivalent(testee.Problems, problems);
        }
    }
}
